#!/bin/bash

set -Eeuo pipefail
shopt -s inherit_errexit

function s3_exists() {
    "${AWS_S3[@]}" ls "${1}" > /dev/null 2>&1
}

function download_encrypted() {
    "${AWS_S3[@]}" cp "${2}" - | cki_openssl_enc ACME_PASSWORD -d | tar xzf - -C "${ACME_DIR}/${1}"
}

function upload_encrypted() {
    tar czf - -C "${ACME_DIR}/${1}" . | cki_openssl_enc ACME_PASSWORD -e | "${AWS_S3[@]}" cp - "${2}"
}

function _dehydrated() {
    dehydrated --config "${DEHYDRATED_CONFIG}" --hook "${BASH_SOURCE[0]}" "$@"
}

function setup() {
    ACME_DIR=$(mktemp -d)
    trap 'rm -rf "$ACME_DIR"' EXIT
    mkdir "${ACME_DIR}"/{accounts,certs}

    # configure dehydrated/lexicon
    DEHYDRATED_CONFIG="${ACME_DIR}/dehydrated.conf"
    {
        echo "BASEDIR='${ACME_DIR}'"
        echo "CHALLENGETYPE='dns-01'"
        echo "HOOK_CHAIN='yes'"
    } > "${DEHYDRATED_CONFIG}"
    # IS_PRODUCTION=false support is incomplete, please be careful! It will
    # interfere with production because
    # - it does not use different file names in the bucket, so it will override
    #   production keys/certs
    # - it will patch the routes
    # - it will update certs on ssh hosts
    if ! cki_is_production; then
        echo "CA='https://acme-staging-v02.api.letsencrypt.org/directory'" >> "${DEHYDRATED_CONFIG}"
    fi
    # shellcheck disable=SC2154
    echo "${ACME_DOMAINS}" > "${ACME_DIR}/domains.txt"
    export LEXICON_ROUTE53_ACCESS_KEY="${ACME_AWS_ACCESS_KEY_ID:-}"
    export LEXICON_ROUTE53_ACCESS_SECRET="${ACME_AWS_SECRET_ACCESS_KEY:-}"

    # configure S3 bucket
    # shellcheck disable=SC2154
    cki_parse_bucket_spec "${ACME_BUCKET}"
    AWS_S3=(aws s3)
    if [[ -n ${AWS_ENDPOINT} ]]; then
        AWS_S3+=(--endpoint "${AWS_ENDPOINT}")
    fi
    # shellcheck disable=SC2154
    S3_PATH="s3://${AWS_BUCKET}/${AWS_BUCKET_PATH}"
    ACCOUNTS_PATH="${S3_PATH}accounts.tar.gz.enc"
    CERTS_PATH="${S3_PATH}certs.tar.gz.enc"

    # configure OpenShift clusters
    export KUBECONFIG="${ACME_DIR}/kube.conf"
    for i in "${!ACME_OPENSHIFT@}"; do
        if [[ "${i}" = *_KEY ]]; then
            local prefix=${i%_KEY}
            prefix=${prefix#ACME_}
            local name=${prefix,,} server=${prefix}_SERVER namespace=${prefix}_PROJECT
            oc config set-credentials "${name}" --token "${!i}"
            oc config set-cluster "${name}" --server "${!server}"
            oc config set-context "${name}" --cluster="${name}" --user="${name}" --namespace "${!namespace}"
        fi
    done > /dev/null 2>&1
}

function check_registration() {
    cki_echo_yellow "Checking registration"
    s3_exists "${ACCOUNTS_PATH}" &
    if wait -n; then
        echo "  Downloading existing registration from ${ACCOUNTS_PATH}... "
        download_encrypted accounts "${ACCOUNTS_PATH}"
    else
        echo "  Creating new registration"
        _dehydrated --register --accept-terms
        echo "  Uploading registration to ${ACCOUNTS_PATH}... "
        upload_encrypted accounts "${ACCOUNTS_PATH}"
    fi
}

function download_certs() {
    cki_echo_yellow "Downloading certificates"
    s3_exists "${CERTS_PATH}" &
    if wait -n; then
        echo "  Downloading existing certificates from ${CERTS_PATH}... "
        download_encrypted certs "${CERTS_PATH}"
    else
        echo "  Nothing found"
    fi
}

function process_certs() {
    cki_echo_yellow "Processing certificates"
    _dehydrated --cron
}

function upload_certs() {
    cki_echo_yellow "Uploading certificates"
    echo "  Uploading certificates to ${CERTS_PATH}... "
    upload_encrypted certs "${CERTS_PATH}"
}

function patch_ssh_hosts() {
    cki_echo_yellow "Patching ssh-able hosts"
    for i in "${!ACME_SSH_@}"; do
        if [[ "${i}" = *_HOST ]]; then
            local prefix=${i%_HOST}
            local host=${prefix}_HOST
            local certificate_name=${prefix}_CERTIFICATE_NAME
            local private_key_path=${prefix}_PRIVATE_KEY_PATH
            local certificate_path=${prefix}_CERTIFICATE_PATH
            local ssh_command=${prefix}_COMMAND ssh_command_array
            local private_key=${prefix#ACME_SSH_}_SSH_PRIVATE_KEY
            # prepare ssh key
            mkdir -p "${ACME_DIR}/ssh-hosts/${!host}"
            printf "%s\n" "${!private_key}" > "${ACME_DIR}/ssh-hosts/${!host}/ssh-key"
            chmod 600 "${ACME_DIR}/ssh-hosts/${!host}/ssh-key"
            # prepare private key
            mkdir -p "${ACME_DIR}/ssh-hosts/${!host}/root/${!private_key_path%/*}"
            cp "${ACME_DIR}/certs/${!certificate_name}/privkey.pem" "${ACME_DIR}/ssh-hosts/${!host}/root/${!private_key_path}"
            chmod 600 "${ACME_DIR}/ssh-hosts/${!host}/root/${!private_key_path}"
            # prepare certificate
            mkdir -p "${ACME_DIR}/ssh-hosts/${!host}/root/${!certificate_path%/*}"
            cp "${ACME_DIR}/certs/${!certificate_name}/fullchain.pem" "${ACME_DIR}/ssh-hosts/${!host}/root/${!certificate_path}"
            # updating
            echo "  Updating ${!host}..."
            tar --create --file - --directory "${ACME_DIR}/ssh-hosts/${!host}/root" . | ssh \
                -o StrictHostKeyChecking=no \
                -o IdentitiesOnly=yes \
                -o IdentityFile="${ACME_DIR}/ssh-hosts/${!host}/ssh-key" \
                "${!host}" \
                sudo tar --extract --file - --directory "/" --no-same-owner --warning=none
            if [[ -v ${ssh_command} ]]; then
                echo "  Restarting via ${!ssh_command}..."
                read -r -a ssh_command_array <<< "${!ssh_command}"
                ssh \
                    -o StrictHostKeyChecking=no \
                    -o IdentitiesOnly=yes \
                    -o IdentityFile="${ACME_DIR}/ssh-hosts/${!host}/ssh-key" \
                    "${!host}" \
                    sudo "${ssh_command_array[@]}"
            fi
        fi
    done
}

function patch_cluster_routes() {
    cki_echo_yellow "Patching cluster routes"
    local namespaces routes route cert patch
    namespaces=${ACME_OPENSHIFT_NAMESPACES:-$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)}
    IFS=',' read -r -a namespaces <<< "${namespaces}"
    for namespace in "${namespaces[@]}"; do
        routes=$(oc get route -n "${namespace}" -o json | jq -r '.items[] |
            {name: .metadata.name,
             acme: .metadata.annotations."cki-project.org/acme"} |
            select(.acme) |
            .name + " " + .acme')
        if [[ -z "${routes}" ]]; then
            continue
        fi
        while read -r route cert; do
            patch=$(jq -n \
                --rawfile fullchain "${ACME_DIR}/certs/${cert}/fullchain.pem" \
                --rawfile privkey "${ACME_DIR}/certs/${cert}/privkey.pem" \
                '{spec: {tls: {certificate: $fullchain, key: $privkey}}}')
            oc patch "route/${route}" -n "${namespace}" -p "${patch}"
        done <<< "${routes}"
    done
}

function patch_remote_routes() {
    cki_echo_yellow "Patching remote routes"
    local contexts context routes route cert patch
    contexts=$(oc config get-contexts -o name)
    while read -r context; do
        echo "  Checking ${context}..."
        routes=$(oc --context "${context}" get route -o json | jq -r '.items[] |
            {name: .metadata.name,
             acme: .metadata.annotations."cki-project.org/acme"} |
            select(.acme) |
            .name + " " + .acme')
        if [[ -z "${routes}" ]]; then
            return 0
        fi
        while read -r route cert; do
            patch=$(jq -n \
                --rawfile fullchain "${ACME_DIR}/certs/${cert}/fullchain.pem" \
                --rawfile privkey "${ACME_DIR}/certs/${cert}/privkey.pem" \
                '{spec: {tls: {certificate: $fullchain, key: $privkey}}}')
            oc --context "${context}" patch "route/${route}" -p "${patch}"
        done <<< "${routes}"
    done <<< "${contexts}"
}

function challenge() {
    local what=$1 delay=$2; shift 2
    while (( $# > 0 )); do
        lexicon route53 "${what}" "${1}" TXT --name="_acme-challenge.${1}." --content="${3}"
        shift 3
    done
    sleep "${delay}"
}

# shellcheck disable=SC1091
. cki_utils.sh

if (( $# == 0 )); then
    echo "Usage: ${0##*/} [certs|patch-remote|patch-local]"
    exit 1
elif [[ $1 = certs ]]; then
    cki_say "acme-certs"
    setup
    check_registration
    download_certs
    process_certs
    upload_certs
elif [[ $1 = patch-remote ]]; then
    cki_say "acme-patch-remote"
    setup
    download_certs
    patch_remote_routes
    patch_ssh_hosts
elif [[ $1 = patch-local ]]; then
    cki_say "acme-patch-local"
    setup
    download_certs
    patch_cluster_routes
elif [[ $1 = deploy_challenge ]]; then
    challenge create 60 "${@:2}"
elif [[ $1 = clean_challenge ]]; then
    challenge delete 0 "${@:2}"
fi
